 <template>
   <div class="upload-file">
    <div 
      class="upload-area"
      :class="{ 'is-dragover': isDragover, 'is-disabled': disabled }"
      @drop.prevent="handleDrop"
      @dragover.prevent="isDragover = true"
      @dragleave.prevent="isDragover = false"
    >
      <div class="upload-content">
        <div class="upload-icon">
          <el-icon size="36" color="#00B4FF"><Upload /></el-icon>
        </div>    
        <div class="upload-text">
          <span>{{ dragText }}</span>
          <div class="upload-hint">{{ hintText }}</div>
        </div>    
        <div class="tech-borders">
          <div class="corner top-left"></div>
          <div class="corner top-right"></div>
          <div class="corner bottom-left"></div>
          <div class="corner bottom-right"></div>
          <div class="border-beam top"></div>
          <div class="border-beam right"></div>
          <div class="border-beam bottom"></div>
          <div class="border-beam left"></div>
        </div>   
        <div class="upload-grid"></div>
      </div> 
      <div class="upload-actions">
        <el-button 
          type="primary" 
          :disabled="disabled"
          @click="handleButtonClick"
          class="tech-button"
        >
          {{ buttonText }}
        </el-button>
        <input 
          ref="fileInput"
          type="file"
          :accept="accept"
          :multiple="multiple"
          style="display: none;"
          @change="handleFileChange"
        />
      </div>
    </div>
    <div v-if="fileList.length > 0" class="upload-file-list">
      <div class="file-list-header">
        <span>已选文件</span>
        <el-button 
          text 
          type="danger" 
          :disabled="disabled" 
          @click="clearFiles"
        >
          清空
        </el-button>
      </div>
      <ul class="file-list">
        <li v-for="(file, index) in fileList" :key="index" class="file-item">
          <div class="file-info">
            <el-icon color="#00B4FF" class="file-icon"><Document /></el-icon>
            <span class="file-name">{{ file.name }}</span>
            <span class="file-size">{{ formatFileSize(file.size) }}</span>
          </div>
          <el-button 
            class="file-delete"
            type="danger" 
            circle 
            :disabled="disabled"
            @click="removeFile(index)"
          >
            <el-icon><Delete /></el-icon>
          </el-button>
        </li>
      </ul>
    </div>
   </div>
 </template>
 
 <script lang="ts">
 import { defineComponent } from 'vue'
 export default defineComponent({
   name: 'UploadFile',
 })
 </script>

<script setup lang="ts">
import { ref,watch } from 'vue'
import { Upload, Document, Delete } from '@element-plus/icons-vue'
interface FileItem {
  name: string;
  size: number;
  type: string;
  file: File;
}
// 定义组件属性
const props = defineProps({
  // 接受的文件类型
  accept: {
    type: String,
    default: '*'
  },
  // 是否支持多文件上传
  multiple: {
    type: Boolean,
    default: false
  },
  // 是否禁用
  disabled: {
    type: Boolean,
    default: false
  },
  // 拖拽区域文本
  dragText: {
    type: String,
    default: '拖拽文件至此区域'
  },
  // 提示文本
  hintText: {
    type: String,
    default: '或点击下方按钮选择文件'
  },
  // 按钮文本
  buttonText: {
    type: String,
    default: '选择文件'
  }
})
const file = defineModel<File | File[] | null>('file', { required: true })
// 定义事件
const emit = defineEmits([
  'file-change'
])
// 状态变量
const isDragover = ref(false)
const fileInput = ref<HTMLInputElement | null>(null)
const fileList = ref<FileItem[]>([])
// 初始化文件列表
watch(() => file.value, (newVal) => {
  if (!newVal || (Array.isArray(newVal) && newVal.length === 0)) {
    fileList.value = []
    return
  }
  // 如果已经有文件列表，且内容一致，不需要更新
  if (fileList.value.length > 0) {
    const currentFiles = props.multiple 
      ? fileList.value.map(item => item.file)
      : fileList.value[0].file
      
    if (JSON.stringify(currentFiles) === JSON.stringify(newVal)) {
      return
    }
  }
  // 更新文件列表
  if (Array.isArray(newVal)) {
    fileList.value = newVal.map(file => ({
      name: file.name,
      size: file.size,
      type: file.type,
      file: file
    }))
  } else if (newVal instanceof File) {
    fileList.value = [{
      name: newVal.name,
      size: newVal.size,
      type: newVal.type,
      file: newVal
    }]
  }
}, { immediate: true })

// 点击按钮选择文件
const handleButtonClick = () => {
  if (fileInput.value) {
    fileInput.value.click()
  }
}
// 处理文件选择
const handleFileChange = (event: Event) => {
  const input = event.target as HTMLInputElement
  if (!input.files?.length) return
  addFiles(Array.from(input.files))
  input.value = '' // 清空input以支持选择相同文件
}
// 处理拖拽
const handleDrop = (event: DragEvent) => {
  isDragover.value = false
  if (!event.dataTransfer?.files) return
  addFiles(Array.from(event.dataTransfer.files))
}
// 添加文件到列表
const addFiles = (files: File[]) => {
  const newFiles = files.map(file => ({
    name: file.name,
    size: file.size,
    type: file.type,
    file: file
  }))
  if (props.multiple) {
    fileList.value.push(...newFiles)
  } else {
    fileList.value = newFiles.slice(0, 1)
  }
  updateModelValue()
}
// 移除单个文件
const removeFile = (index: number) => {
  fileList.value.splice(index, 1)
  updateModelValue()
}
// 清空文件列表
const clearFiles = () => {
  fileList.value = []
  updateModelValue()
}
// 更新v-model绑定值
const updateModelValue = () => {
  if (fileList.value.length === 0) {
    file.value = props.multiple ? [] : null
    emit('file-change', props.multiple ? [] : null)
    return
  }
  file.value = props.multiple ? fileList.value.map(item => item.file) : fileList.value[0].file
  emit('file-change', file.value)
}
// 格式化文件大小
const formatFileSize = (size: number) => {
  if (size < 1024) {
    return size + ' B'
  } else if (size < 1024 * 1024) {
    return (size / 1024).toFixed(1) + ' KB'
  } else if (size < 1024 * 1024 * 1024) {
    return (size / (1024 * 1024)).toFixed(1) + ' MB'
  } else {
    return (size / (1024 * 1024 * 1024)).toFixed(1) + ' GB'
  }
}
// 暴露方法给父组件
defineExpose({
  clear: clearFiles,
  getFiles: () => fileList.value.map(item => item.file)
})
 </script>
 
<style lang="scss" scoped>
.upload-file {
  width: 100%;
  
  .upload-area {
    border: 1px dashed rgba(0, 180, 255, 0.5);
    border-radius: 8px;
    background: linear-gradient(
      180deg, 
      rgba(15, 25, 38, 0.5) 0%, 
      rgba(18, 28, 40, 0.55) 50%, 
      rgba(20, 30, 45, 0.5) 100%
    );
    padding: 20px;
    margin-bottom: 16px;
    transition: all 0.3s;
    position: relative;
    overflow: hidden;
    cursor: pointer;
    
    &:hover {
      border-color: rgba(0, 180, 255, 0.8);
      box-shadow:
        inset 0 0 20px rgba(0, 140, 255, 0.05),
        0 0 15px rgba(0, 150, 255, 0.1);
        
      .tech-borders {
        .corner {
          opacity: 1;
          width: 15px;
          height: 15px;
        }
        
        .border-beam {
          opacity: 1;
        }
      }
    }
    
    &.is-dragover {
      border-color: rgba(0, 180, 255, 0.9);
      background: linear-gradient(
        180deg, 
        rgba(15, 40, 65, 0.5) 0%, 
        rgba(18, 45, 75, 0.55) 50%, 
        rgba(20, 50, 80, 0.5) 100%
      );
      box-shadow:
        inset 0 0 30px rgba(0, 140, 255, 0.1),
        0 0 20px rgba(0, 150, 255, 0.2);
        
      .tech-borders {
        .corner {
          opacity: 1;
          width: 20px;
          height: 20px;
          border-width: 2px;
        }
        
        .border-beam {
          opacity: 1;
          animation-duration: 1.5s;
        }
      }
      
      .upload-grid {
        opacity: 0.3;
      }
    }
    
    &.is-disabled {
      cursor: not-allowed;
      opacity: 0.6;
      pointer-events: none;
    }
    
    .upload-content {
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      padding: 30px;
      position: relative;
      
      .upload-icon {
        margin-bottom: 16px;
      }
      
      .upload-text {
        text-align: center;
        color: rgba(255, 255, 255, 0.9);
        font-size: 16px;
        font-weight: 500;
        margin-bottom: 8px;
        text-shadow: 0 0 5px rgba(0, 180, 255, 0.3);
        
        .upload-hint {
          font-size: 13px;
          color: rgba(255, 255, 255, 0.6);
          margin-top: 8px;
        }
      }
    }
    
    .tech-borders {
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      pointer-events: none;
      
      .corner {
        position: absolute;
        width: 10px;
        height: 10px;
        opacity: 0.5;
        transition: all 0.3s ease;
        
        &.top-left {
          top: 0;
          left: 0;
          border-top: 1px solid rgba(0, 180, 255, 0.8);
          border-left: 1px solid rgba(0, 180, 255, 0.8);
        }
        
        &.top-right {
          top: 0;
          right: 0;
          border-top: 1px solid rgba(0, 180, 255, 0.8);
          border-right: 1px solid rgba(0, 180, 255, 0.8);
        }
        
        &.bottom-left {
          bottom: 0;
          left: 0;
          border-bottom: 1px solid rgba(0, 180, 255, 0.8);
          border-left: 1px solid rgba(0, 180, 255, 0.8);
        }
        
        &.bottom-right {
          bottom: 0;
          right: 0;
          border-bottom: 1px solid rgba(0, 180, 255, 0.8);
          border-right: 1px solid rgba(0, 180, 255, 0.8);
        }
      }
      
      .border-beam {
        position: absolute;
        opacity: 0.3;
        background: linear-gradient(
          90deg,
          transparent,
          rgba(0, 180, 255, 0.8),
          transparent
        );
        filter: blur(1px);
        animation: border-beam 3s linear infinite;
        
        &.top {
          top: 0;
          left: 20%;
          height: 1px;
          width: 60px;
        }
        
        &.right {
          top: 20%;
          right: 0;
          height: 60px;
          width: 1px;
          animation-delay: 0.75s;
          background: linear-gradient(
            180deg,
            transparent,
            rgba(0, 180, 255, 0.8),
            transparent
          );
        }
        
        &.bottom {
          bottom: 0;
          right: 20%;
          height: 1px;
          width: 60px;
          animation-delay: 1.5s;
        }
        
        &.left {
          bottom: 20%;
          left: 0;
          height: 60px;
          width: 1px;
          animation-delay: 2.25s;
          background: linear-gradient(
            180deg,
            transparent,
            rgba(0, 180, 255, 0.8),
            transparent
          );
        }
      }
    }
    
    .upload-grid {
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      background-image: 
        linear-gradient(rgba(30, 80, 140, 0.05) 1px, transparent 1px),
        linear-gradient(90deg, rgba(30, 80, 140, 0.05) 1px, transparent 1px);
      background-size: 20px 20px;
      background-position: center;
      opacity: 0.15;
      pointer-events: none;
      z-index: 1;
    }
    
    .upload-actions {
      display: flex;
      justify-content: center;
      margin-top: 10px;
      position: relative;
      z-index: 2;
    }
  }
  
  .tech-button {
    background: linear-gradient(
      90deg,
      rgba(0, 60, 120, 0.7) 0%,
      rgba(0, 100, 180, 0.8) 50%,
      rgba(0, 60, 120, 0.7) 100%
    );
    border: 1px solid rgba(0, 180, 255, 0.5);
    color: rgba(255, 255, 255, 0.95);
    font-weight: 500;
    letter-spacing: 0.5px;
    padding: 10px 24px;
    border-radius: 4px;
    transition: all 0.3s;
    position: relative;
    overflow: hidden;
    
    &:hover, &:focus {
      background: linear-gradient(
        90deg,
        rgba(0, 80, 160, 0.7) 0%,
        rgba(0, 120, 200, 0.8) 50%,
        rgba(0, 80, 160, 0.7) 100%
      );
      border-color: rgba(0, 180, 255, 0.8);
      transform: translateY(-1px);
      box-shadow: 0 0 15px rgba(0, 180, 255, 0.3);
    }
    
    &:before {
      content: '';
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 1px;
      background: linear-gradient(
        90deg,
        transparent,
        rgba(0, 180, 255, 0.5),
        transparent
      );
      animation: flow-light 3s infinite ease-in-out;
    }
  }
  
  .upload-file-list {
    margin-top: 20px;
    border: 1px solid rgba(0, 100, 180, 0.3);
    border-radius: 6px;
    background: rgba(15, 25, 38, 0.5);
    overflow: hidden;
    
    .file-list-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 10px 16px;
      background: linear-gradient(
        90deg,
        rgba(15, 25, 38, 0.9) 0%,
        rgba(22, 35, 55, 0.95) 50%,
        rgba(15, 25, 38, 0.9) 100%
      );
      border-bottom: 1px solid rgba(0, 100, 180, 0.3);
      
      span {
        font-size: 14px;
        color: rgba(255, 255, 255, 0.9);
        font-weight: 500;
      }
    }
    
    .file-list {
      list-style: none;
      margin: 0;
      padding: 0;
      max-height: 260px;
      overflow-y: auto;
      
      .file-item {
        display: flex;
        justify-content: space-between;
        align-items: center;
        padding: 10px 16px;
        border-bottom: 1px solid rgba(0, 100, 180, 0.2);
        transition: background-color 0.3s;
        
        &:last-child {
          border-bottom: none;
        }
        
        &:hover {
          background-color: rgba(0, 60, 120, 0.15);
        }
        
        .file-info {
          display: flex;
          align-items: center;
          
          .file-icon {
            margin-right: 12px;
          }
          
          .file-name {
            color: rgba(255, 255, 255, 0.9);
            font-size: 14px;
            margin-right: 12px;
            font-weight: 500;
            text-shadow: 0 0 2px rgba(0, 180, 255, 0.2);
          }
          
          .file-size {
            color: rgba(255, 255, 255, 0.6);
            font-size: 12px;
          }
        }
        
        .file-delete {
          padding: 4px;
        }
      }
    }
  }
}

@keyframes border-beam {
  0% {
    left: -60px;
    opacity: 0.8;
  }
  100% {
    left: calc(100% + 60px);
    opacity: 0.8;
  }
}

@keyframes flow-light {
  0% {
    opacity: 0.5;
    background-position: -100% 0;
  }
  50% {
    opacity: 0.9;
  }
  100% {
    opacity: 0.5;
    background-position: 200% 0;
  }
}
</style>
 